use md5::{Digest, Md5};

use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom};

pub const MAGIC_NUMBER: &str = "CAFEEFAC";
pub const TAIL_LEN: usize = "CAFEEFAC5DE33C873FEF33D1BC0DD52277370640".as_bytes().len();

// 写入
pub fn write_magic_md5(file_path: &str, md5: &String) -> io::Result<()> {
    let bytes_to_append = md5.as_bytes();
    let mut file = OpenOptions::new().append(true).open(file_path)?;
    file.write_all(&bytes_to_append)?;
    Ok(())
}

// 读取
pub fn read_magic_md5(file_path: &str) -> io::Result<String> {
    // 打开文件并创建 BufReader
    let file = File::open(file_path)?;
    // 获取文件长度
    let len = file.metadata().unwrap().len();
    if len < TAIL_LEN as u64 {
        return Err(io::Error::new(io::ErrorKind::Other, "no more data"));
    }
    let mut reader = BufReader::new(file);
    // 移动文件指针到需要读取的位置
    reader.seek(SeekFrom::Start(len - TAIL_LEN as u64))?;
    let mut buffer = vec![0; TAIL_LEN as usize];
    reader.read_exact(&mut buffer)?;
    // println!("Last 10 bytes: {:?}", &buffer);
    //转化为字符串
    let s = String::from_utf8_lossy(&buffer);
    // println!("Last 10 bytes: {:?}", s);
    Ok(s.to_string())
}

/// 计算 md5
/// file: 文件
/// is_skip:是否跳跃计算
/// exclude_tail_len :要排除的尾部数据长度
pub fn md5(file_path: &str, is_skip: bool, exclude_tail_len: usize) -> io::Result<String> {
    // xue.mp4
    // 5DE33C873FEF33D1BC0DD52277370640
    // ms2: 15
    // 5de33c873fef33d1bc0dd52277370640
    // 耗时: 30.3455ms
    // llava
    // 34DBBC20D45931E64BAC6D328010EF1B
    // ms2: 7 309
    // 34dbbc20d45931e64bac6d328010ef1b
    // 耗时: 41.0317099s
    let now = std::time::Instant::now();
    let file = File::open(file_path)?;
    // Find the length of the file
    let len = file.metadata().unwrap().len() as usize;
    // 参与计算的数据长度
    let total_bytes_to_read = len - exclude_tail_len;
    // Decide on a reasonable buffer size (1MB in this case, fastest will depend on hardware)
    let buf_len = len.min(1024 * 128) as usize;
    let mut br = BufReader::with_capacity(buf_len, file);
    let mut md5 = Md5::new();

    // 读多少次
    let mut part_count_to_read = (total_bytes_to_read + buf_len - 1) / buf_len;
    // 剩余多少
    let bytes_remain = total_bytes_to_read % buf_len;

    loop {
        // println!("part_count_to_read={},bytes_remain={}",part_count_to_read,bytes_remain);
        if part_count_to_read <= 0 {
            break;
        }
        // Get a chunk of the file
        let part = br.fill_buf().unwrap();
        // If that chunk was empty, the reader has reached EOF
        if part.is_empty() {
            break;
        }
        let part_len = part.len();
        if !is_skip {
            //是否跳跃计算
            if part_count_to_read > 1 || bytes_remain == 0 {
                // Add chunk to the md5
                md5.update(part);
            } else {
                md5.update(&part[0..bytes_remain]);
            }
        } else {
            let mut min_len = 8.min(part_len);
            if part_count_to_read == 1 {
                min_len = min_len.min(bytes_remain);
            }
            md5.update(&part[0..min_len]);
        }
        // Tell the buffer that the chunk is consumed
        br.consume(part_len);
        part_count_to_read -= 1;
    }
    let result = md5.finalize();
    let md5str = format!("{:x}", result).to_uppercase();
    // println!("md5: {}", md5str);
    let elapsed = now.elapsed();
    println!("md5耗时: {:?}", elapsed);

    Ok(md5str)
}

/// 生成录屏文件的checksum
pub fn generate_checksum(file: &str) -> Option<String> {
    // println!("filename:{}", file);
    // 写入
    if let Ok(magic_md5_r) = read_magic_md5(file) {
        if magic_md5_r.starts_with(MAGIC_NUMBER) {
            log::error!("stop_recording find  checksum:");
            // 如果存在直接返回
            return Some(magic_md5_r[MAGIC_NUMBER.len()..].to_string());
        } else if !magic_md5_r.starts_with(MAGIC_NUMBER) {
            //否则重新计算后返回
            //println!("不是合法文件");
            // 计算
            if let Ok(md5str) = md5(file, true, 0) {
                //println!("compute md5 :{}", md5str);
                let magic_md5_w = format!("{}{}", MAGIC_NUMBER, &md5str);
                if let Ok(_) = write_magic_md5(file, &magic_md5_w) {
                    return Some(md5str);
                }
            }
        }
    }
    None
}

pub fn validate_checksum(file: &str) -> Result<String, String> {
    println!("filename:{}", file);
    // 写入
    if let Ok(magic_md5_r) = read_magic_md5(file) {
        if magic_md5_r.starts_with(MAGIC_NUMBER) {
            println!("文件魔数合法,开始验证是否被修改");
            // 计算
            if let Ok(md5str) = md5(file, true, TAIL_LEN) {
                let r = md5str.eq_ignore_ascii_case(&magic_md5_r[MAGIC_NUMBER.len()..]);
                println!(
                    "没被修改:{},  origin md5:{} , compute md5 :{}",
                    r,
                    &magic_md5_r[MAGIC_NUMBER.len()..],
                    md5str
                );
                return Ok(md5str);
            }
        } else {
            println!("文件魔数不匹配");
        }
    }
    Err("非法文件".to_string())
}
